From 858f8ebf80dd3abb8275c72dabcc83d78637490f Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 30 Mar 2026 10:35:55 -0700 Subject: [PATCH] CVE-2026-5223: prohibit unpacking symlinks and other unexpected entries MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Cargo has historically not allowed creating .crate packages containing symlinks. (It packages the symlink target in place of the symlink, instead.) So, any package containing a symlink would have to be hand-constructed. Such packages are also not allowed on crates.io, so it could only come from an alternate registry. Rather than dealing with symlink traversal attacks when unpacking a crate, just prohibit symlinks entirely. In the process, also prohibit other kinds of unusual entries. As an exception, allow character devices but warn about them, because some exist in crates on crates.io. FG: backported from 1.96.0 Signed-off-by: Fabian Grünbichler Gbp-Pq: Topic cargo Gbp-Pq: Name CVE-2026-5223-prohibit-unpacking-symlinks-and-other-unexp.patch --- .../cargo/src/cargo/sources/registry/mod.rs | 11 ++++++- src/tools/cargo/tests/testsuite/registry.rs | 31 ++++++++++--------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/tools/cargo/src/cargo/sources/registry/mod.rs b/src/tools/cargo/src/cargo/sources/registry/mod.rs index bf10f81fc2..313258fc9d 100644 --- a/src/tools/cargo/src/cargo/sources/registry/mod.rs +++ b/src/tools/cargo/src/cargo/sources/registry/mod.rs @@ -197,7 +197,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing}; use flate2::read::GzDecoder; use serde::Deserialize; use serde::Serialize; -use tar::Archive; +use tar::{Archive, EntryType}; use tracing::debug; use crate::core::dependency::Dependency; @@ -662,6 +662,15 @@ impl<'gctx> RegistrySource<'gctx> { prefix ) } + + // Prevent unpacking symlinks and other unexpected entry types + match entry.header().entry_type() { + EntryType::Regular | EntryType::Directory => {} + t => anyhow::bail!( + "invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}", + ), + } + // Prevent unpacking the lockfile from the crate itself. if entry_path .file_name() diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs index 8498713701..df922efbdf 100644 --- a/src/tools/cargo/tests/testsuite/registry.rs +++ b/src/tools/cargo/tests/testsuite/registry.rs @@ -3213,8 +3213,7 @@ fn package_lock_inside_package_is_overwritten() { } #[cargo_test] -fn package_lock_as_a_symlink_inside_package_is_overwritten() { - let registry = registry::init(); +fn package_lock_as_a_symlink_inside_package_is_invalid() { let p = project() .file( "Cargo.toml", @@ -3237,21 +3236,23 @@ fn package_lock_as_a_symlink_inside_package_is_overwritten() { .symlink(".cargo-ok", "src/lib.rs") .publish(); - p.cargo("check").run(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`) +[ERROR] failed to download replaced source registry `crates-io` - let id = SourceId::for_registry(registry.index_url()).unwrap(); - let hash = cargo::util::hex::short_hash(&id); - let pkg_root = paths::cargo_home() - .join("registry") - .join("src") - .join(format!("-{}", hash)) - .join("bar-0.0.1"); - let ok = pkg_root.join(".cargo-ok"); - let librs = pkg_root.join("src/lib.rs"); +Caused by: + failed to unpack package `bar v0.0.1 (registry `dummy-registry`)` - // Is correctly overwritten and doesn't affect the file linked to - assert_eq!(ok.metadata().unwrap().len(), 7); - assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}"); +Caused by: + invalid tarball downloaded, contains an entry at "bar-0.0.1/.cargo-ok" with invalid type Symlink + +"#]]) + .run(); } #[cargo_test] -- 2.30.2